package bg.ittalents.homework.airport;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;

public class LoggingDispatcher implements Dispatcher {

	private static final int MAXIMUM_TRACKS = 100;
	private static final int MINIMUM_TRACKS = 1;
	private static final int MAXIMUM_AIRPORT_CAPACITY = 100;
	private static final int MINIMUM_AIRPORT_CAPACITY = 0;
	private static final int MAXIMUM_TIME_TO_LAND = 99;
	private static final int MINIMUM_TIME_TO_LAND = 0;

	private final int capacity;
	private final int timeToLandOrTakeOff;
	private int runwaysCount;
	private PriorityQueue<Runway> takenOrClosedRunways;
	private LinkedList<Runway> availableRunways;
	private HashMap<Integer, Jet> landedPlanes;
	private EventLogger logger;
	private Reader reader;
	private PriorityQueue<Event> events;
	private int numberOfLandedPlanes;

	public LoggingDispatcher(int runwaysCount, EventLogger logger,
			Reader reader, int timeToLand, int capacity) {

		if (!validateTracksCount(runwaysCount) || logger == null
				|| reader == null || !validateTimeToLand(timeToLand)
				|| !validateCapacity(capacity)) {
			throw new IllegalArgumentException();
		}

		takenOrClosedRunways = new PriorityQueue<Runway>(11, new RunwayComp());
		availableRunways = new LinkedList<Runway>();
		events = new PriorityQueue<Event>(11, new EventsComp());
		landedPlanes = new HashMap<Integer, Jet>();

		this.logger = logger;
		this.reader = reader;
		this.timeToLandOrTakeOff = timeToLand;
		this.capacity = capacity;

		for (int i = 1; i <= runwaysCount; i++) {
			addNewRunway(new Runway(i));
		}
	}

	private boolean validateCapacity(int capacity) {
		return capacity >= MINIMUM_AIRPORT_CAPACITY
				&& capacity < MAXIMUM_AIRPORT_CAPACITY;
	}

	private boolean validateTimeToLand(int time) {
		return time >= MINIMUM_TIME_TO_LAND && time < MAXIMUM_TIME_TO_LAND;
	}

	private boolean validateTracksCount(int runwaysCount) {
		return runwaysCount < MAXIMUM_TRACKS || runwaysCount > MINIMUM_TRACKS;
	}

	public boolean addNewRunway(Runway runway) {

		if (runwaysCount < MAXIMUM_TRACKS) {

			if (runway.isAvailable()) {
				availableRunways.add(runway);

			} else {
				takenOrClosedRunways.add(runway);
			}
			runwaysCount++;
			return true;
		}
		return false;
	}

	private Runway findAvailableRunwayInTime(long time) {
		if (time > timeToLandOrTakeOff && !availableRunways.isEmpty()) {
			Runway temp = availableRunways.peekFirst();
			availableRunways.removeFirst();
			return temp;
		}

		if (!takenOrClosedRunways.isEmpty()
				&& takenOrClosedRunways.peek().notAvailableUntil()
						+ timeToLandOrTakeOff < time) {
			return takenOrClosedRunways.poll();
		}
		return null;
	}

	private void land(Jet jet, Runway runway) {

		events.add(new Event(jet.getId() + " lands on " + runway.getId(),
				timeToLandOrTakeOff));

		runway.reserve(timeToLandOrTakeOff);
		takenOrClosedRunways.add(runway);
		
		numberOfLandedPlanes++;
	}

	private void takeOff(Jet jet, Runway runway) {

		events.add(new Event(
				jet.getId() + " takses off from " + runway.getId(),
				timeToLandOrTakeOff));

		runway.reserve(timeToLandOrTakeOff);
		takenOrClosedRunways.add(runway);
		
		numberOfLandedPlanes--;
	}

	@Override
	public void requestLanding(Jet jet) {
		Runway temp = findAvailableRunwayInTime(jet.getRemainingFuel());

		if (temp != null && numberOfLandedPlanes < capacity) {
			land(jet, temp);
		} else {
			divertPlane(jet);
		}
	}

	// razchitame na korektnost na izhodniq fail i ne obrabotvame exception-a
	private void divertPlane(Jet jet) {

		try {
			logger.log(jet.getId() + " Diverted");
			decrementEventsTime();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void requestTakeOff(Jet jet) {
		Iterator<Runway> availIterator = availableRunways.iterator();
		boolean sucess = false;

		while (availIterator.hasNext()) {

			Runway temp = availIterator.next();

			if (temp.getId() == jet.getLandedOnRunway()) {
				takeOff(jet, temp);
				sucess = true;
				break;
			}
		}

		if (!sucess) {
			if (!availableRunways.isEmpty()) {
				takeOff(jet, availableRunways.getFirst());
			} else {
				Runway temp = takenOrClosedRunways.poll();
				takeOff(jet, temp);
			}
		}

	}

	@Override
	public void closeRunway(int id, long period) {
		Iterator<Runway> availIterator = availableRunways.iterator();

		while (availIterator.hasNext()) {
			Runway temp = availIterator.next();
			if (temp.getId() == id) {
				temp.close(period);
				decrementEventsTime();
			}
		}
	}

	private void decrementEventsTime() {
		for (Event e : events) {
			e.decrementTime();
		}
	}

	private boolean readInputEvent() {

		if (!reader.hasNextEvent()) {
			return false;
		}

		String inputEvent = reader.getNextEvent();

		if (inputEvent.contains("close")) {

			int closingId = StringHelper.getFirstInt(inputEvent);
			int closingPeriod = StringHelper.getLastInt(inputEvent);

			closeRunway(closingId, closingPeriod);
		} else if (inputEvent.contains("off")) {

			int planeId = StringHelper.getFirstInt(inputEvent);

			if (landedPlanes.containsKey(planeId)) {

				Jet takingOffJet = landedPlanes.get(planeId);
				requestTakeOff(takingOffJet);
			}
		} else if (inputEvent.contains("land")) {

			int landingId = StringHelper.getFirstInt(inputEvent);
			int landingFuel = StringHelper.getLastInt(inputEvent);

			Jet landingJet = new Jet(landingId, landingFuel);
			requestLanding(landingJet);
		}

		decrementEventsTime();
		return true;
	}

	public void executeSchedule() {

		readInputEvent();

		while (!events.isEmpty() || reader.hasNextEvent()) {

			while (!events.isEmpty() && events.peek().getRemainingTime() == 0) {

				try {
					logger.log(events.peek().getData());
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
				events.poll();
				availableRunways.add(takenOrClosedRunways.poll());
				decrementEventsTime();
			}

			if (!reader.hasNextEvent() && !events.isEmpty()) {
				decrementEventsTime();
			}

			readInputEvent();

		}
	}

}
